package com.cd.university.config.security.authentication.jwt;

import com.cd.university.common.utils.JwtUtils;
import com.cd.university.config.security.properties.SecurityProperties;
import com.cd.university.db.pojo.User;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

/**
 * 如果请求头里面右Token，那么对请求头里面的token进行验证
 * 如果请求头里面没有Token，那么直接放行
 * @author 陈栋
 * @create 2021/9/12 11:00
 */
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private ObjectMapper objectMapper = new ObjectMapper();

    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {
        // 如果请求头里面包含了Token字段那么就会进行验证，否则就不会过这个Filter
        String token = isHeaderToken(request);
        if (token!=null) {
            // 不管token是否过期，只要包含了正确信息，就可以进行解析
            // 从token中拿到UserDetails
            UserDetails userDetails = jwtUtils.getUserDetails(token);
            String username = userDetails.getUsername();
            if (username==null) {
                throw new JwtExceptionHandler("jwt解析错误，未找到用户");
            }
            // 通过UserDetails中的username拿到User
            User user = jwtUtils.getUser(username);
            if (user==null) {
                throw new JwtExceptionHandler("jwt解析错误，未找到用户");
            }
            Integer userId = user.getUserId();

            // 从redis中拿到登陆成功时的redisToken
            String redisToken = (String) redisTemplate.opsForHash().get(securityProperties.getJwt().getHeader(), String.valueOf(userId));

            // 进行双令牌的验证
            validateToken(userDetails, userId, response, token, redisToken);

            // 如果验证通过，生成一个已经认证的JwtToken
            JwtAuthenticationToken authenticationToken = new JwtAuthenticationToken(userDetails,
                    userDetails.getAuthorities());
            // 向全局中放入认证过的Authentication子类，后面的过滤器就不会再进行认证
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        filterChain.doFilter(request,response);
    }

    /**
     * 进行双令牌的验证
     * @param userDetails 用户信息
     * @param userId 用户id
     * @param response 响应
     * @param token 前端传过来的Token
     * @param redisToken redis中取出的Token
     */
    private void validateToken(UserDetails userDetails,
                               Integer userId,
                               HttpServletResponse response,
                               String token,
                               String redisToken) throws IOException {
        String validateToken = jwtUtils.validateToken(token, redisToken);

        // 如果jwt验证令牌的时候返回null，那么说明两个令牌都已经失效
        if (validateToken==null) {
            throw new JwtExceptionHandler("无效令牌,请重新登录");
        }

        // 如果返回的令牌被刷新了，那么说明前端纯过来的token过期了，但是redis中的没有过期，此时刷新两个令牌
        if (!validateToken.equals(token)) {
            // 向响应中写入新的刷新令牌
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(
                    Map.of("Refresh" + securityProperties.getJwt().getHeader(), validateToken)
            ));

            // 向redis中更新令牌
            redisTemplate.opsForHash().put(securityProperties.getJwt().getHeader(),
                    String.valueOf(userId),
                    jwtUtils.createRedisToken(userDetails));
        }


    }

    /**
     * 判断请求头里面是否有Token
     * @param request 前端请求
     * @return 布尔值
     */
    private String isHeaderToken(HttpServletRequest request) {
        return request.getHeader(securityProperties.getJwt().getHeader());
    }
}
